home *** CD-ROM | disk | FTP | other *** search
/ Tech Arsenal 1 / Tech Arsenal (Arsenal Computer).ISO / tek-02 / comp-pas.zip / TUTORPAS.ZIP / TUTOR11.DOC < prev    next >
Text File  |  1989-06-03  |  55KB  |  1,976 lines

  1. OPA A
  2.  
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28.  
  29.                             LET'S BUILD A COMPILER!
  30.  
  31.                                        By
  32.  
  33.                             Jack W. Crenshaw, Ph.D.
  34.  
  35.                                   3 June 1989
  36.  
  37.  
  38.                         Part XI: LEXICAL SCAN REVISITED
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67. PA A
  68.  
  69.  
  70.  
  71.  
  72.  
  73.        *****************************************************************
  74.        *                                                               *
  75.        *                        COPYRIGHT NOTICE                       *
  76.        *                                                               *
  77.        *   Copyright (C) 1989 Jack W. Crenshaw. All rights reserved.   *
  78.        *                                                               *
  79.        *****************************************************************
  80.  
  81.  
  82.        INTRODUCTION
  83.  
  84.        I've got some  good news and some bad news.  The bad news is that
  85.        this installment is  not  the  one  I promised last time.  What's
  86.        more, the one after this one won't be, either.
  87.  
  88.        The good news is the reason for this installment:  I've  found  a
  89.        way  to simplify and improve the lexical  scanning  part  of  the
  90.        compiler.  Let me explain.
  91.  
  92.  
  93.        BACKGROUND
  94.  
  95.        If  you'll remember, we talked at length  about  the  subject  of
  96.        lexical  scanners in Part VII, and I left you with a design for a
  97.        distributed scanner that I felt was about as simple  as  I  could
  98.        make it ... more than most that I've  seen  elsewhere.    We used
  99.        that idea in Part X.  The compiler structure  that  resulted  was
  100.        simple, and it got the job done.
  101.  
  102.        Recently, though, I've begun  to  have  problems, and they're the
  103.        kind that send a message that you might be doing something wrong.
  104.  
  105.        The  whole thing came to a head when I tried to address the issue
  106.        of  semicolons.  Several people have asked  me  about  them,  and
  107.        whether or not KISS will have them separating the statements.  My
  108.        intention has been NOT to  use semicolons, simply because I don't
  109.        like them and, as you can see, they have not proved necessary.
  110.  
  111.        But I know that many of you, like me, have  gotten  used to them,
  112.        and so  I  set  out  to write a short installment to show you how
  113.        they could easily be added, if you were so inclined.
  114.  
  115.        Well, it  turned  out  that  they weren't easy to add at all.  In
  116.        fact it was darned difficult.
  117.  
  118.        I guess I should have  realized that something was wrong, because
  119.        of the issue  of  newlines.    In the last couple of installments
  120.        we've addressed that issue,  and  I've shown you how to deal with
  121.        newlines with a  procedure called, appropriately enough, NewLine.
  122.        In  TINY  Version  1.0,  I  sprinkled calls to this procedure  in
  123.        strategic spots in the code.
  124.  
  125.        It  seems  that  every time I've addressed the issue of newlines,
  126.        though,  I've found it to be tricky,  and  the  resulting  parserA*A*
  127.                                      - 2 -
  128. PA A
  129.  
  130.  
  131.  
  132.  
  133.  
  134.        turned out to be quite fragile ... one addition or  deletion here
  135.        or  there and things tended to go to pot.  Looking back on it,  I
  136.        realize that  there  was  a  message  in  this that I just wasn't
  137.        paying attention to.
  138.  
  139.        When I tried to add semicolons  on  top of the newlines, that was
  140.        the last straw.   I ended up with much too complex a solution.  I
  141.        began to realize that something fundamental had to change.
  142.  
  143.        So,  in  a  way this installment will cause us to backtrack a bit
  144.        and revisit the issue of scanning all over again.    Sorry  about
  145.        that.  That's the price you pay for watching me  do  this in real
  146.        time.  But the new version is definitely an improvement, and will
  147.        serve us well for what is to come.
  148.  
  149.        As  I said, the scanner we used in Part X was about as simple  as
  150.        one can get.  But anything can be improved.   The  new scanner is
  151.        more like the classical  scanner,  and  not  as simple as before.
  152.        But the overall  compiler  structure is even simpler than before.
  153.        It's also more robust, and easier to add  to  and/or  modify.   I
  154.        think that's worth the time spent in this digression.  So in this
  155.        installment, I'll be showing  you  the  new  structure.  No doubt
  156.        you'll  be  happy  to  know  that, while the changes affect  many
  157.        procedures, they aren't very profound  and so we lose very little
  158.        of what's been done so far.
  159.  
  160.        Ironically, the new scanner  is  much  more conventional than the
  161.        old one, and is very much like the more generic scanner  I showed
  162.        you  earlier  in  Part VII.  Then I started trying to get clever,
  163.        and I almost clevered myself clean out of business.   You'd think
  164.        one day I'd learn: K-I-S-S!
  165.  
  166.  
  167.        THE PROBLEM
  168.  
  169.        The problem begins to show  itself in procedure Block, which I've
  170.        reproduced below:
  171.  
  172.  
  173.        {--------------------------------------------------------------}
  174.        { Parse and Translate a Block of Statements }
  175.  
  176.        procedure Block;
  177.        begin
  178.           Scan;
  179.           while not(Token in ['e', 'l']) do begin
  180.              case Token of
  181.               'i': DoIf;
  182.               'w': DoWhile;
  183.               'R': DoRead;
  184.               'W': DoWrite;
  185.              else Assignment;
  186.              end;
  187.              Scan;A*A*
  188.                                      - 3 -
  189. PA A
  190.  
  191.  
  192.  
  193.  
  194.  
  195.           end;
  196.        end;
  197.        {--------------------------------------------------------------}
  198.  
  199.  
  200.        As  you   can  see,  Block  is  oriented  to  individual  program
  201.        statements.  At each pass through  the  loop, we know that we are
  202.        at  the beginning of a statement.  We exit the block when we have
  203.        scanned an END or an ELSE.
  204.  
  205.        But suppose that we see a semicolon instead.   The  procedure  as
  206.        it's shown above  can't  handle that, because procedure Scan only
  207.        expects and can only accept tokens that begin with a letter.
  208.  
  209.        I  tinkered  around for quite awhile to come up with a  fix.    I
  210.        found many possible approaches, but none were very satisfying.  I
  211.        finally figured out the reason.
  212.  
  213.        Recall that when we started with our single-character parsers, we
  214.        adopted a convention that the lookahead character would always be
  215.        prefetched.    That   is,   we  would  have  the  character  that
  216.        corresponds to our  current  position in the input stream fetched
  217.        into the global character Look, so that we could  examine  it  as
  218.        many  times  as  needed.    The  rule  we  adopted was that EVERY
  219.        recognizer, if it found its target token, would  advance  Look to
  220.        the next character in the input stream.
  221.  
  222.        That simple and fixed convention served us very well when  we had
  223.        single-character tokens, and it still does.  It would make  a lot
  224.        of sense to apply the same rule to multi-character tokens.
  225.  
  226.        But when we got into lexical scanning, I began  to  violate  that
  227.        simple rule.  The scanner of Part X  did  indeed  advance  to the
  228.        next token if it found an identifier or keyword, but it DIDN'T do
  229.        that if it found a carriage return, a whitespace character, or an
  230.        operator.
  231.  
  232.        Now, that sort of mixed-mode  operation gets us into deep trouble
  233.        in procedure Block, because whether or not the  input  stream has
  234.        been advanced depends upon the kind of token we  encounter.    If
  235.        it's  a keyword or the target of  an  assignment  statement,  the
  236.        "cursor," as defined by the contents of Look,  has  been advanced
  237.        to  the next token OR to the beginning of whitespace.  If, on the
  238.        other  hand,  the  token  is  a  semicolon,  or if we have hit  a
  239.        carriage return, the cursor has NOT advanced.
  240.  
  241.        Needless to say, we can add enough logic  to  keep  us  on track.
  242.        But it's tricky, and makes the whole parser very fragile.
  243.  
  244.        There's a much  better  way,  and  that's just to adopt that same
  245.        rule that's worked so well before, to apply to TOKENS as  well as
  246.        single characters.  In other words, we'll prefetch tokens just as
  247.        we've always done for  characters.   It seems so obvious once you
  248.        think about it that way.A*A*
  249.                                      - 4 -
  250. PA A
  251.  
  252.  
  253.  
  254.  
  255.  
  256.        Interestingly enough, if we do things this way  the  problem that
  257.        we've had with newline characters goes away.  We  can  just  lump
  258.        them in as  whitespace  characters, which means that the handling
  259.        of  newlines  becomes  very trivial, and MUCH less prone to error
  260.        than we've had to deal with in the past.
  261.  
  262.  
  263.        THE SOLUTION
  264.  
  265.        Let's  begin  to  fix  the  problem  by  re-introducing  the  two
  266.        procedures:
  267.  
  268.        {--------------------------------------------------------------}
  269.        { Get an Identifier }
  270.  
  271.        procedure GetName;
  272.        begin
  273.           SkipWhite;
  274.           if Not IsAlpha(Look) then Expected('Identifier');
  275.           Token := 'x';
  276.           Value := '';
  277.           repeat
  278.              Value := Value + UpCase(Look);
  279.              GetChar;
  280.           until not IsAlNum(Look);
  281.        end;
  282.  
  283.  
  284.        {--------------------------------------------------------------}
  285.        { Get a Number }
  286.  
  287.        procedure GetNum;
  288.        begin
  289.           SkipWhite;
  290.           if not IsDigit(Look) then Expected('Number');
  291.           Token := '#';
  292.           Value := '';
  293.           repeat
  294.              Value := Value + Look;
  295.              GetChar;
  296.           until not IsDigit(Look);
  297.        end;
  298.        {--------------------------------------------------------------}
  299.  
  300.  
  301.        These two procedures are  functionally  almost  identical  to the
  302.        ones  I  showed  you in Part VII.  They each  fetch  the  current
  303.        token, either an identifier or a number, into  the  global string
  304.        Value.    They  also  set  the  encoded  version, Token,  to  the
  305.        appropriate code.  The input  stream is left with Look containing
  306.        the first character NOT part of the token.
  307.  
  308.        We  can do the same thing  for  operators,  even  multi-character
  309.        operators, with a procedure such as:A*A*
  310.                                      - 5 -
  311. PA A
  312.  
  313.  
  314.  
  315.  
  316.  
  317.        {--------------------------------------------------------------}
  318.        { Get an Operator }
  319.  
  320.        procedure GetOp;
  321.        begin
  322.           Token := Look;
  323.           Value := '';
  324.           repeat
  325.              Value := Value + Look;
  326.              GetChar;
  327.           until IsAlpha(Look) or IsDigit(Look) or IsWhite(Look);
  328.        end;
  329.        {--------------------------------------------------------------}
  330.  
  331.        Note  that  GetOp  returns,  as  its  encoded  token,  the  FIRST
  332.        character of the operator.  This is important,  because  it means
  333.        that we can now use that single character to  drive  the  parser,
  334.        instead of the lookahead character.
  335.  
  336.        We need to tie these  procedures together into a single procedure
  337.        that can handle all three  cases.  The  following  procedure will
  338.        read any one of the token types and always leave the input stream
  339.        advanced beyond it:
  340.  
  341.  
  342.        {--------------------------------------------------------------}
  343.        { Get the Next Input Token }
  344.  
  345.        procedure Next;
  346.        begin
  347.           SkipWhite;
  348.           if IsAlpha(Look) then GetName
  349.           else if IsDigit(Look) then GetNum
  350.           else GetOp;
  351.        end;
  352.        {--------------------------------------------------------------}
  353.  
  354.  
  355.        ***NOTE  that  here  I have put SkipWhite BEFORE the calls rather
  356.        than after.  This means that, in general, the variable  Look will
  357.        NOT have a meaningful value in it, and therefore  we  should  NOT
  358.        use it as a test value for parsing, as we have been doing so far.
  359.        That's the big departure from our normal approach.
  360.  
  361.        Now, remember that before I was careful not to treat the carriage
  362.        return (CR) and line  feed  (LF) characters as white space.  This
  363.        was  because,  with  SkipWhite  called  as the last thing in  the
  364.        scanner, the encounter with  LF  would  trigger a read statement.
  365.        If we were on the last line of the program,  we  couldn't get out
  366.        until we input another line with a non-white  character.   That's
  367.        why I needed the second procedure, NewLine, to handle the CRLF's.
  368.  
  369.        But now, with the call  to SkipWhite coming first, that's exactly
  370.        the behavior we want.    The  compiler  must know there's anotherA*A*
  371.                                      - 6 -
  372. PA A
  373.  
  374.  
  375.  
  376.  
  377.  
  378.        token coming or it wouldn't be calling Next.  In other words,  it
  379.        hasn't found the terminating  END  yet.  So we're going to insist
  380.        on more data until we find something.
  381.  
  382.        All this means that we can greatly simplify both the  program and
  383.        the concepts, by treating CR and LF as whitespace characters, and
  384.        eliminating NewLine.  You  can  do  that  simply by modifying the
  385.        function IsWhite:
  386.  
  387.  
  388.        {--------------------------------------------------------------}
  389.        { Recognize White Space }
  390.  
  391.        function IsWhite(c: char): boolean;
  392.        begin
  393.           IsWhite := c in [' ', TAB, CR, LF];
  394.        end;
  395.        {--------------------------------------------------------------}
  396.  
  397.  
  398.        We've already tried similar routines in Part VII,  but  you might
  399.        as well try these new ones out.  Add them to a copy of the Cradle
  400.        and call Next with the following main program:
  401.  
  402.  
  403.        {--------------------------------------------------------------}
  404.        { Main Program }
  405.  
  406.        begin
  407.           Init;
  408.           repeat
  409.              Next;
  410.              WriteLn(Token, ' ', Value);
  411.           until Token = '.';
  412.        end.
  413.        {--------------------------------------------------------------}
  414.  
  415.  
  416.        Compile  it and verify that you can separate  a  program  into  a
  417.        series of tokens, and that you get the right  encoding  for  each
  418.        token.
  419.  
  420.        This ALMOST works,  but  not  quite.    There  are  two potential
  421.        problems:    First,  in KISS/TINY almost all of our operators are
  422.        single-character operators.  The only exceptions  are  the relops
  423.        >=, <=, and <>.  It seems  a  shame  to  treat  all  operators as
  424.        strings and do a  string  compare,  when  only a single character
  425.        compare  will  almost  always  suffice.   Second, and  much  more
  426.        important, the  thing  doesn't  WORK  when  two  operators appear
  427.        together, as in (a+b)*(c+d).  Here the string following 'b' would
  428.        be interpreted as a single operator ")*(."
  429.  
  430.        It's possible to fix that problem.  For example,  we  could  just
  431.        give GetOp a  list  of  legal  characters, and we could treat theA*A*
  432.                                      - 7 -
  433. PA A
  434.  
  435.  
  436.  
  437.  
  438.  
  439.        parentheses as different operator types  than  the  others.   But
  440.        this begins to get messy.
  441.  
  442.        Fortunately, there's a  better  way that solves all the problems.
  443.        Since almost  all the operators are single characters, let's just
  444.        treat  them  that  way, and let GetOp get only one character at a
  445.        time.  This not only simplifies GetOp, but also speeds  things up
  446.        quite a  bit.    We  still have the problem of the relops, but we
  447.        were treating them as special cases anyway.
  448.  
  449.        So here's the final version of GetOp:
  450.  
  451.  
  452.        {--------------------------------------------------------------}
  453.        { Get an Operator }
  454.  
  455.        procedure GetOp;
  456.        begin
  457.           SkipWhite;
  458.           Token := Look;
  459.           Value := Look;
  460.           GetChar;
  461.        end;
  462.        {--------------------------------------------------------------}
  463.  
  464.  
  465.        Note that I still give the string Value a value.  If you're truly
  466.        concerned about efficiency, you could leave this out.  When we're
  467.        expecting an operator, we will only be testing  Token  anyhow, so
  468.        the  value of the string won't matter.  But to me it seems to  be
  469.        good practice to give the thing a value just in case.
  470.  
  471.        Try  this  new  version with some realistic-looking  code.    You
  472.        should  be  able  to  separate  any program into  its  individual
  473.        tokens, with the  caveat  that the two-character relops will scan
  474.        into two separate tokens.  That's OK ... we'll  parse  them  that
  475.        way.
  476.  
  477.        Now, in Part VII the function of Next was combined with procedure
  478.        Scan,  which  also  checked every identifier against  a  list  of
  479.        keywords and encoded each one that was found.  As I  mentioned at
  480.        the time, the last thing we would want  to  do  is  to use such a
  481.        procedure in places where keywords  should not appear, such as in
  482.        expressions.  If we  did  that, the keyword list would be scanned
  483.        for every identifier appearing in the code.  Not good.
  484.  
  485.        The  right  way  to  deal  with  that  is  to simply separate the
  486.        functions  of  fetching  tokens and looking for  keywords.    The
  487.        version of Scan shown below  does NOTHING but check for keywords.
  488.        Notice that it operates on the current token and does NOT advance
  489.        the input stream.
  490.  
  491.  
  492.        {--------------------------------------------------------------}A*A*
  493.                                      - 8 -
  494. PA A
  495.  
  496.  
  497.  
  498.  
  499.  
  500.        { Scan the Current Identifier for Keywords }
  501.  
  502.        procedure Scan;
  503.        begin
  504.           if Token = 'x' then
  505.              Token := KWcode[Lookup(Addr(KWlist), Value, NKW) + 1];
  506.        end;
  507.        {--------------------------------------------------------------}
  508.  
  509.  
  510.        There is one last detail.  In the compiler there are a few places
  511.        that we must  actually  check  the  string  value  of  the token.
  512.        Mainly, this  is done to distinguish between the different END's,
  513.        but there are a couple  of  other  places.    (I  should  note in
  514.        passing that we could always  eliminate the need for matching END
  515.        characters by encoding each one  to a different character.  Right
  516.        now we are definitely taking the lazy man's route.)
  517.  
  518.        The  following  version  of MatchString takes the  place  of  the
  519.        character-oriented Match.  Note that, like Match, it DOES advance
  520.        the input stream.
  521.  
  522.  
  523.        {--------------------------------------------------------------}
  524.        { Match a Specific Input String }
  525.  
  526.        procedure MatchString(x: string);
  527.        begin
  528.           if Value <> x then Expected('''' + x + '''');
  529.           Next;
  530.        end;
  531.        {--------------------------------------------------------------}
  532.  
  533.  
  534.        FIXING UP THE COMPILER
  535.  
  536.        Armed with these new scanner procedures, we can now begin  to fix
  537.        the compiler to  use  them  properly.   The changes are all quite
  538.        minor,  but  there  are quite a  few  places  where  changes  are
  539.        necessary.  Rather than  showing  you each place, I will give you
  540.        the general idea and then just give the finished product.
  541.  
  542.  
  543.        First of all, the code for procedure Block doesn't change, though
  544.        its function does:
  545.  
  546.  
  547.        {--------------------------------------------------------------}
  548.        { Parse and Translate a Block of Statements }
  549.  
  550.        procedure Block;
  551.        begin
  552.           Scan;
  553.           while not(Token in ['e', 'l']) do beginA*A*
  554.                                      - 9 -
  555. PA A
  556.  
  557.  
  558.  
  559.  
  560.  
  561.              case Token of
  562.               'i': DoIf;
  563.               'w': DoWhile;
  564.               'R': DoRead;
  565.               'W': DoWrite;
  566.              else Assignment;
  567.              end;
  568.              Scan;
  569.           end;
  570.        end;
  571.        {--------------------------------------------------------------}
  572.  
  573.  
  574.        Remember that the new version of Scan doesn't  advance  the input
  575.        stream, it only  scans  for  keywords.   The input stream must be
  576.        advanced by each procedure that Block calls.
  577.  
  578.        In general, we have to replace every test on Look with  a similar
  579.        test on Token.  For example:
  580.  
  581.  
  582.        {---------------------------------------------------------------}
  583.        { Parse and Translate a Boolean Expression }
  584.  
  585.        procedure BoolExpression;
  586.        begin
  587.           BoolTerm;
  588.           while IsOrOp(Token) do begin
  589.              Push;
  590.              case Token of
  591.               '|': BoolOr;
  592.               '~': BoolXor;
  593.              end;
  594.           end;
  595.        end;
  596.        {--------------------------------------------------------------}
  597.  
  598.  
  599.        In procedures like Add, we don't  have  to use Match anymore.  We
  600.        need only call Next to advance the input stream:
  601.  
  602.  
  603.        {--------------------------------------------------------------}
  604.        { Recognize and Translate an Add }
  605.  
  606.        procedure Add;
  607.        begin
  608.           Next;
  609.           Term;
  610.           PopAdd;
  611.        end;
  612.        {-------------------------------------------------------------}ABAB
  613.                                     - 10 -A*A*
  614. PA A
  615.  
  616.  
  617.  
  618.  
  619.  
  620.        Control  structures  are  actually simpler.  We just call Next to
  621.        advance over the control keywords:
  622.  
  623.  
  624.        {---------------------------------------------------------------}
  625.        { Recognize and Translate an IF Construct }
  626.  
  627.        procedure Block; Forward;
  628.  
  629.        procedure DoIf;
  630.        var L1, L2: string;
  631.        begin
  632.           Next;
  633.           BoolExpression;
  634.           L1 := NewLabel;
  635.           L2 := L1;
  636.           BranchFalse(L1);
  637.           Block;
  638.           if Token = 'l' then begin
  639.              Next;
  640.              L2 := NewLabel;
  641.              Branch(L2);
  642.              PostLabel(L1);
  643.              Block;
  644.           end;
  645.           PostLabel(L2);
  646.           MatchString('ENDIF');
  647.        end;
  648.        {--------------------------------------------------------------}
  649.  
  650.  
  651.        That's about the extent of the REQUIRED changes.  In  the listing
  652.        of TINY  Version  1.1  below,  I've  also  made a number of other
  653.        "improvements" that  aren't really required.  Let me explain them
  654.        briefly:
  655.  
  656.         (1)  I've deleted the two procedures Prog and Main, and combined
  657.              their functions into the main program.  They didn't seem to
  658.              add  to program clarity ... in fact  they  seemed  to  just
  659.              muddy things up a little.
  660.  
  661.         (2)  I've  deleted  the  keywords  PROGRAM  and  BEGIN  from the
  662.              keyword list.  Each  one  only occurs in one place, so it's
  663.              not necessary to search for it.
  664.  
  665.         (3)  Having been  bitten  by  an  overdose  of  cleverness, I've
  666.              reminded myself that TINY  is  supposed  to be a minimalist
  667.              program.  Therefore I've  replaced  the  fancy  handling of
  668.              unary minus with the dumbest one I could think of.  A giant
  669.              step backwards in code quality, but a  great simplification
  670.              of the compiler.  KISS is the right place to use  the other
  671.              version.ABAB
  672.                                     - 11 -A*A*
  673. PA A
  674.  
  675.  
  676.  
  677.  
  678.  
  679.         (4)  I've added some  error-checking routines such as CheckTable
  680.              and CheckDup, and  replaced  in-line code by calls to them.
  681.              This cleans up a number of routines.
  682.  
  683.         (5)  I've  taken  the  error  checking  out  of  code generation
  684.              routines  like Store, and put it in  the  parser  where  it
  685.              belongs.  See Assignment, for example.
  686.  
  687.         (6)  There was an error in InTable and Locate  that  caused them
  688.              to search all locations  instead  of  only those with valid
  689.              data  in them.  They now search only  valid  cells.    This
  690.              allows us to eliminate  the  initialization  of  the symbol
  691.              table, which was done in Init.
  692.  
  693.         (7)  Procedure AddEntry now has two  arguments,  which  helps to
  694.              make things a bit more modular.
  695.  
  696.         (8)  I've cleaned up the  code  for  the relational operators by
  697.              the addition of the  new  procedures  CompareExpression and
  698.              NextExpression.
  699.  
  700.         (9)  I fixed an error in the Read routine ... the  earlier value
  701.              did not check for a valid variable name.
  702.  
  703.  
  704.         CONCLUSION
  705.  
  706.        The resulting compiler for  TINY  is given below.  Other than the
  707.        removal  of  the  keyword PROGRAM, it parses the same language as
  708.        before.    It's  just  a  bit cleaner, and more importantly  it's
  709.        considerably more robust.  I feel good about it.
  710.  
  711.        The next installment will be another  digression:  the discussion
  712.        of  semicolons  and  such that got me into this mess in the first
  713.        place.  THEN we'll press on  into  procedures and types.  Hang in
  714.        there with me.  The addition of those features will go a long way
  715.        towards removing KISS from  the  "toy  language" category.  We're
  716.        getting very close to being able to write a serious compiler.
  717.  
  718.  
  719.        TINY VERSION 1.1
  720.  
  721.  
  722.        {--------------------------------------------------------------}
  723.        program Tiny11;
  724.  
  725.        {--------------------------------------------------------------}
  726.        { Constant Declarations }
  727.  
  728.        const TAB = ^I;
  729.              CR  = ^M;
  730.              LF  = ^J;
  731.  
  732.              LCount: integer = 0;A*A*
  733.                                     - 12 -
  734. PA A
  735.  
  736.  
  737.  
  738.  
  739.  
  740.              NEntry: integer = 0;
  741.  
  742.  
  743.        {--------------------------------------------------------------}
  744.        { Type Declarations }
  745.  
  746.        type Symbol = string[8];
  747.  
  748.             SymTab = array[1..1000] of Symbol;
  749.  
  750.             TabPtr = ^SymTab;
  751.  
  752.  
  753.        {--------------------------------------------------------------}
  754.        { Variable Declarations }
  755.  
  756.        var Look : char;             { Lookahead Character }
  757.            Token: char;             { Encoded Token       }
  758.            Value: string[16];       { Unencoded Token     }
  759.  
  760.  
  761.        const MaxEntry = 100;
  762.  
  763.        var ST   : array[1..MaxEntry] of Symbol;
  764.            SType: array[1..MaxEntry] of char;
  765.  
  766.  
  767.        {--------------------------------------------------------------}
  768.        { Definition of Keywords and Token Types }
  769.  
  770.        const NKW =   9;
  771.              NKW1 = 10;
  772.  
  773.        const KWlist: array[1..NKW] of Symbol =
  774.                      ('IF', 'ELSE', 'ENDIF', 'WHILE', 'ENDWHILE',
  775.                       'READ', 'WRITE', 'VAR', 'END');
  776.  
  777.        const KWcode: string[NKW1] = 'xileweRWve';
  778.  
  779.  
  780.        {--------------------------------------------------------------}
  781.        { Read New Character From Input Stream }
  782.  
  783.        procedure GetChar;
  784.        begin
  785.           Read(Look);
  786.        end;
  787.  
  788.        {--------------------------------------------------------------}
  789.        { Report an Error }
  790.  
  791.        procedure Error(s: string);
  792.        begin
  793.           WriteLn;A*A*
  794.                                     - 13 -
  795. PA A
  796.  
  797.  
  798.  
  799.  
  800.  
  801.           WriteLn(^G, 'Error: ', s, '.');
  802.        end;
  803.  
  804.  
  805.        {--------------------------------------------------------------}
  806.        { Report Error and Halt }
  807.  
  808.        procedure Abort(s: string);
  809.        begin
  810.           Error(s);
  811.           Halt;
  812.        end;
  813.  
  814.  
  815.        {--------------------------------------------------------------}
  816.        { Report What Was Expected }
  817.  
  818.        procedure Expected(s: string);
  819.        begin
  820.           Abort(s + ' Expected');
  821.        end;
  822.  
  823.        {--------------------------------------------------------------}
  824.        { Report an Undefined Identifier }
  825.  
  826.        procedure Undefined(n: string);
  827.        begin
  828.           Abort('Undefined Identifier ' + n);
  829.        end;
  830.  
  831.  
  832.        {--------------------------------------------------------------}
  833.        { Report a Duplicate Identifier }
  834.  
  835.        procedure Duplicate(n: string);
  836.        begin
  837.           Abort('Duplicate Identifier ' + n);
  838.        end;
  839.  
  840.  
  841.        {--------------------------------------------------------------}
  842.        { Check to Make Sure the Current Token is an Identifier }
  843.  
  844.        procedure CheckIdent;
  845.        begin
  846.           if Token <> 'x' then Expected('Identifier');
  847.        end;
  848.  
  849.  
  850.        {--------------------------------------------------------------}
  851.        { Recognize an Alpha Character }
  852.  
  853.        function IsAlpha(c: char): boolean;
  854.        beginA*A*
  855.                                     - 14 -
  856. PA A
  857.  
  858.  
  859.  
  860.  
  861.  
  862.           IsAlpha := UpCase(c) in ['A'..'Z'];
  863.        end;
  864.  
  865.  
  866.        {--------------------------------------------------------------}
  867.        { Recognize a Decimal Digit }
  868.  
  869.        function IsDigit(c: char): boolean;
  870.        begin
  871.           IsDigit := c in ['0'..'9'];
  872.        end;
  873.  
  874.  
  875.        {--------------------------------------------------------------}
  876.        { Recognize an AlphaNumeric Character }
  877.  
  878.        function IsAlNum(c: char): boolean;
  879.        begin
  880.           IsAlNum := IsAlpha(c) or IsDigit(c);
  881.        end;
  882.  
  883.  
  884.        {--------------------------------------------------------------}
  885.        { Recognize an Addop }
  886.  
  887.        function IsAddop(c: char): boolean;
  888.        begin
  889.           IsAddop := c in ['+', '-'];
  890.        end;
  891.  
  892.  
  893.        {--------------------------------------------------------------}
  894.        { Recognize a Mulop }
  895.  
  896.        function IsMulop(c: char): boolean;
  897.        begin
  898.           IsMulop := c in ['*', '/'];
  899.        end;
  900.  
  901.  
  902.        {--------------------------------------------------------------}
  903.        { Recognize a Boolean Orop }
  904.  
  905.        function IsOrop(c: char): boolean;
  906.        begin
  907.           IsOrop := c in ['|', '~'];
  908.        end;
  909.  
  910.  
  911.        {--------------------------------------------------------------}
  912.        { Recognize a Relop }
  913.  
  914.        function IsRelop(c: char): boolean;
  915.        beginA*A*
  916.                                     - 15 -
  917. PA A
  918.  
  919.  
  920.  
  921.  
  922.  
  923.           IsRelop := c in ['=', '#', '<', '>'];
  924.        end;
  925.  
  926.  
  927.        {--------------------------------------------------------------}
  928.        { Recognize White Space }
  929.  
  930.        function IsWhite(c: char): boolean;
  931.        begin
  932.           IsWhite := c in [' ', TAB, CR, LF];
  933.        end;
  934.  
  935.  
  936.        {--------------------------------------------------------------}
  937.        { Skip Over Leading White Space }
  938.  
  939.        procedure SkipWhite;
  940.        begin
  941.           while IsWhite(Look) do
  942.              GetChar;
  943.        end;
  944.  
  945.  
  946.        {--------------------------------------------------------------}
  947.        { Table Lookup }
  948.  
  949.        function Lookup(T: TabPtr; s: string; n: integer): integer;
  950.        var i: integer;
  951.            found: Boolean;
  952.        begin
  953.           found := false;
  954.           i := n;
  955.           while (i > 0) and not found do
  956.              if s = T^[i] then
  957.                 found := true
  958.              else
  959.                 dec(i);
  960.           Lookup := i;
  961.        end;
  962.  
  963.  
  964.        {--------------------------------------------------------------}
  965.        { Locate a Symbol in Table }
  966.        { Returns the index of the entry.  Zero if not present. }
  967.  
  968.        function Locate(N: Symbol): integer;
  969.        begin
  970.           Locate := Lookup(@ST, n, NEntry);
  971.        end;
  972.  
  973.  
  974.        {--------------------------------------------------------------}
  975.        { Look for Symbol in Table }A6A6
  976.                                     - 16 -A*A*
  977. PA A
  978.  
  979.  
  980.  
  981.  
  982.  
  983.        function InTable(n: Symbol): Boolean;
  984.        begin
  985.           InTable := Lookup(@ST, n, NEntry) <> 0;
  986.        end;
  987.  
  988.  
  989.        {--------------------------------------------------------------}
  990.        { Check to See if an Identifier is in the Symbol Table         }
  991.        { Report an error if it's not. }
  992.  
  993.  
  994.        procedure CheckTable(N: Symbol);
  995.        begin
  996.           if not InTable(N) then Undefined(N);
  997.        end;
  998.  
  999.  
  1000.        {--------------------------------------------------------------}
  1001.        { Check the Symbol Table for a Duplicate Identifier }
  1002.        { Report an error if identifier is already in table. }
  1003.  
  1004.  
  1005.        procedure CheckDup(N: Symbol);
  1006.        begin
  1007.           if InTable(N) then Duplicate(N);
  1008.        end;
  1009.  
  1010.  
  1011.        {--------------------------------------------------------------}
  1012.        { Add a New Entry to Symbol Table }
  1013.  
  1014.        procedure AddEntry(N: Symbol; T: char);
  1015.        begin
  1016.           CheckDup(N);
  1017.           if NEntry = MaxEntry then Abort('Symbol Table Full');
  1018.           Inc(NEntry);
  1019.           ST[NEntry] := N;
  1020.           SType[NEntry] := T;
  1021.        end;
  1022.  
  1023.  
  1024.        {--------------------------------------------------------------}
  1025.        { Get an Identifier }
  1026.  
  1027.        procedure GetName;
  1028.        begin
  1029.           SkipWhite;
  1030.           if Not IsAlpha(Look) then Expected('Identifier');
  1031.           Token := 'x';
  1032.           Value := '';
  1033.           repeat
  1034.              Value := Value + UpCase(Look);
  1035.              GetChar;
  1036.           until not IsAlNum(Look);A*A*
  1037.                                     - 17 -
  1038. PA A
  1039.  
  1040.  
  1041.  
  1042.  
  1043.  
  1044.        end;
  1045.  
  1046.  
  1047.        {--------------------------------------------------------------}
  1048.        { Get a Number }
  1049.  
  1050.        procedure GetNum;
  1051.        begin
  1052.           SkipWhite;
  1053.           if not IsDigit(Look) then Expected('Number');
  1054.           Token := '#';
  1055.           Value := '';
  1056.           repeat
  1057.              Value := Value + Look;
  1058.              GetChar;
  1059.           until not IsDigit(Look);
  1060.        end;
  1061.  
  1062.  
  1063.        {--------------------------------------------------------------}
  1064.        { Get an Operator }
  1065.  
  1066.        procedure GetOp;
  1067.        begin
  1068.           SkipWhite;
  1069.           Token := Look;
  1070.           Value := Look;
  1071.           GetChar;
  1072.        end;
  1073.  
  1074.  
  1075.        {--------------------------------------------------------------}
  1076.        { Get the Next Input Token }
  1077.  
  1078.        procedure Next;
  1079.        begin
  1080.           SkipWhite;
  1081.           if IsAlpha(Look) then GetName
  1082.           else if IsDigit(Look) then GetNum
  1083.           else GetOp;
  1084.        end;
  1085.  
  1086.  
  1087.        {--------------------------------------------------------------}
  1088.        { Scan the Current Identifier for Keywords }
  1089.  
  1090.        procedure Scan;
  1091.        begin
  1092.           if Token = 'x' then
  1093.              Token := KWcode[Lookup(Addr(KWlist), Value, NKW) + 1];
  1094.        end;
  1095.  
  1096.  
  1097.        {--------------------------------------------------------------}A*A*
  1098.                                     - 18 -
  1099. PA A
  1100.  
  1101.  
  1102.  
  1103.  
  1104.  
  1105.        { Match a Specific Input String }
  1106.  
  1107.        procedure MatchString(x: string);
  1108.        begin
  1109.           if Value <> x then Expected('''' + x + '''');
  1110.           Next;
  1111.        end;
  1112.  
  1113.  
  1114.        {--------------------------------------------------------------}
  1115.        { Output a String with Tab }
  1116.  
  1117.        procedure Emit(s: string);
  1118.        begin
  1119.           Write(TAB, s);
  1120.        end;
  1121.  
  1122.  
  1123.        {--------------------------------------------------------------}
  1124.        { Output a String with Tab and CRLF }
  1125.  
  1126.        procedure EmitLn(s: string);
  1127.        begin
  1128.           Emit(s);
  1129.           WriteLn;
  1130.        end;
  1131.  
  1132.  
  1133.        {--------------------------------------------------------------}
  1134.        { Generate a Unique Label }
  1135.  
  1136.        function NewLabel: string;
  1137.        var S: string;
  1138.        begin
  1139.           Str(LCount, S);
  1140.           NewLabel := 'L' + S;
  1141.           Inc(LCount);
  1142.        end;
  1143.  
  1144.  
  1145.        {--------------------------------------------------------------}
  1146.        { Post a Label To Output }
  1147.  
  1148.        procedure PostLabel(L: string);
  1149.        begin
  1150.           WriteLn(L, ':');
  1151.        end;
  1152.  
  1153.  
  1154.        {---------------------------------------------------------------}
  1155.        { Clear the Primary Register }
  1156.  
  1157.        procedure Clear;
  1158.        beginA*A*
  1159.                                     - 19 -
  1160. PA A
  1161.  
  1162.  
  1163.  
  1164.  
  1165.  
  1166.           EmitLn('CLR D0');
  1167.        end;
  1168.  
  1169.  
  1170.        {---------------------------------------------------------------}
  1171.        { Negate the Primary Register }
  1172.  
  1173.        procedure Negate;
  1174.        begin
  1175.           EmitLn('NEG D0');
  1176.        end;
  1177.  
  1178.  
  1179.        {---------------------------------------------------------------}
  1180.        { Complement the Primary Register }
  1181.  
  1182.        procedure NotIt;
  1183.        begin
  1184.           EmitLn('NOT D0');
  1185.        end;
  1186.  
  1187.  
  1188.        {---------------------------------------------------------------}
  1189.        { Load a Constant Value to Primary Register }
  1190.  
  1191.        procedure LoadConst(n: string);
  1192.        begin
  1193.           Emit('MOVE #');
  1194.           WriteLn(n, ',D0');
  1195.        end;
  1196.  
  1197.  
  1198.        {---------------------------------------------------------------}
  1199.        { Load a Variable to Primary Register }
  1200.  
  1201.        procedure LoadVar(Name: string);
  1202.        begin
  1203.           if not InTable(Name) then Undefined(Name);
  1204.           EmitLn('MOVE ' + Name + '(PC),D0');
  1205.        end;
  1206.  
  1207.  
  1208.        {---------------------------------------------------------------}
  1209.        { Push Primary onto Stack }
  1210.  
  1211.        procedure Push;
  1212.        begin
  1213.           EmitLn('MOVE D0,-(SP)');
  1214.        end;
  1215.  
  1216.  
  1217.        {---------------------------------------------------------------}
  1218.        { Add Top of Stack to Primary }A6A6
  1219.                                     - 20 -A*A*
  1220. PA A
  1221.  
  1222.  
  1223.  
  1224.  
  1225.  
  1226.        procedure PopAdd;
  1227.        begin
  1228.           EmitLn('ADD (SP)+,D0');
  1229.        end;
  1230.  
  1231.  
  1232.        {---------------------------------------------------------------}
  1233.        { Subtract Primary from Top of Stack }
  1234.  
  1235.        procedure PopSub;
  1236.        begin
  1237.           EmitLn('SUB (SP)+,D0');
  1238.           EmitLn('NEG D0');
  1239.        end;
  1240.  
  1241.  
  1242.        {---------------------------------------------------------------}
  1243.        { Multiply Top of Stack by Primary }
  1244.  
  1245.        procedure PopMul;
  1246.        begin
  1247.           EmitLn('MULS (SP)+,D0');
  1248.        end;
  1249.  
  1250.  
  1251.        {---------------------------------------------------------------}
  1252.        { Divide Top of Stack by Primary }
  1253.  
  1254.        procedure PopDiv;
  1255.        begin
  1256.           EmitLn('MOVE (SP)+,D7');
  1257.           EmitLn('EXT.L D7');
  1258.           EmitLn('DIVS D0,D7');
  1259.           EmitLn('MOVE D7,D0');
  1260.        end;
  1261.  
  1262.  
  1263.        {---------------------------------------------------------------}
  1264.        { AND Top of Stack with Primary }
  1265.  
  1266.        procedure PopAnd;
  1267.        begin
  1268.           EmitLn('AND (SP)+,D0');
  1269.        end;
  1270.  
  1271.  
  1272.        {---------------------------------------------------------------}
  1273.        { OR Top of Stack with Primary }
  1274.  
  1275.        procedure PopOr;
  1276.        begin
  1277.           EmitLn('OR (SP)+,D0');
  1278.        end;A6A6
  1279.                                     - 21 -A*A*
  1280. PA A
  1281.  
  1282.  
  1283.  
  1284.  
  1285.  
  1286.        {---------------------------------------------------------------}
  1287.        { XOR Top of Stack with Primary }
  1288.  
  1289.        procedure PopXor;
  1290.        begin
  1291.           EmitLn('EOR (SP)+,D0');
  1292.        end;
  1293.  
  1294.  
  1295.        {---------------------------------------------------------------}
  1296.        { Compare Top of Stack with Primary }
  1297.  
  1298.        procedure PopCompare;
  1299.        begin
  1300.           EmitLn('CMP (SP)+,D0');
  1301.        end;
  1302.  
  1303.  
  1304.        {---------------------------------------------------------------}
  1305.        { Set D0 If Compare was = }
  1306.  
  1307.        procedure SetEqual;
  1308.        begin
  1309.           EmitLn('SEQ D0');
  1310.           EmitLn('EXT D0');
  1311.        end;
  1312.  
  1313.  
  1314.        {---------------------------------------------------------------}
  1315.        { Set D0 If Compare was != }
  1316.  
  1317.        procedure SetNEqual;
  1318.        begin
  1319.           EmitLn('SNE D0');
  1320.           EmitLn('EXT D0');
  1321.        end;
  1322.  
  1323.  
  1324.        {---------------------------------------------------------------}
  1325.        { Set D0 If Compare was > }
  1326.  
  1327.        procedure SetGreater;
  1328.        begin
  1329.           EmitLn('SLT D0');
  1330.           EmitLn('EXT D0');
  1331.        end;
  1332.  
  1333.  
  1334.        {---------------------------------------------------------------}
  1335.        { Set D0 If Compare was < }
  1336.  
  1337.        procedure SetLess;
  1338.        begin
  1339.           EmitLn('SGT D0');A*A*
  1340.                                     - 22 -
  1341. PA A
  1342.  
  1343.  
  1344.  
  1345.  
  1346.  
  1347.           EmitLn('EXT D0');
  1348.        end;
  1349.  
  1350.  
  1351.        {---------------------------------------------------------------}
  1352.        { Set D0 If Compare was <= }
  1353.  
  1354.        procedure SetLessOrEqual;
  1355.        begin
  1356.           EmitLn('SGE D0');
  1357.           EmitLn('EXT D0');
  1358.        end;
  1359.  
  1360.  
  1361.        {---------------------------------------------------------------}
  1362.        { Set D0 If Compare was >= }
  1363.  
  1364.        procedure SetGreaterOrEqual;
  1365.        begin
  1366.           EmitLn('SLE D0');
  1367.           EmitLn('EXT D0');
  1368.        end;
  1369.  
  1370.  
  1371.        {---------------------------------------------------------------}
  1372.        { Store Primary to Variable }
  1373.  
  1374.        procedure Store(Name: string);
  1375.        begin
  1376.           EmitLn('LEA ' + Name + '(PC),A0');
  1377.           EmitLn('MOVE D0,(A0)')
  1378.        end;
  1379.  
  1380.  
  1381.        {---------------------------------------------------------------}
  1382.        { Branch Unconditional  }
  1383.  
  1384.        procedure Branch(L: string);
  1385.        begin
  1386.           EmitLn('BRA ' + L);
  1387.        end;
  1388.  
  1389.  
  1390.        {---------------------------------------------------------------}
  1391.        { Branch False }
  1392.  
  1393.        procedure BranchFalse(L: string);
  1394.        begin
  1395.           EmitLn('TST D0');
  1396.           EmitLn('BEQ ' + L);
  1397.        end;
  1398.  
  1399.  
  1400.        {---------------------------------------------------------------}A*A*
  1401.                                     - 23 -
  1402. PA A
  1403.  
  1404.  
  1405.  
  1406.  
  1407.  
  1408.        { Read Variable to Primary Register }
  1409.  
  1410.        procedure ReadIt(Name: string);
  1411.        begin
  1412.           EmitLn('BSR READ');
  1413.           Store(Name);
  1414.        end;
  1415.  
  1416.  
  1417.        { Write from Primary Register }
  1418.  
  1419.        procedure WriteIt;
  1420.        begin
  1421.           EmitLn('BSR WRITE');
  1422.        end;
  1423.  
  1424.  
  1425.        {--------------------------------------------------------------}
  1426.        { Write Header Info }
  1427.  
  1428.        procedure Header;
  1429.        begin
  1430.           WriteLn('WARMST', TAB, 'EQU $A01E');
  1431.        end;
  1432.  
  1433.  
  1434.        {--------------------------------------------------------------}
  1435.        { Write the Prolog }
  1436.  
  1437.        procedure Prolog;
  1438.        begin
  1439.           PostLabel('MAIN');
  1440.        end;
  1441.  
  1442.  
  1443.        {--------------------------------------------------------------}
  1444.        { Write the Epilog }
  1445.  
  1446.        procedure Epilog;
  1447.        begin
  1448.           EmitLn('DC WARMST');
  1449.           EmitLn('END MAIN');
  1450.        end;
  1451.  
  1452.  
  1453.        {---------------------------------------------------------------}
  1454.        { Allocate Storage for a Static Variable }
  1455.  
  1456.        procedure Allocate(Name, Val: string);
  1457.        begin
  1458.           WriteLn(Name, ':', TAB, 'DC ', Val);
  1459.        end;ABAB
  1460.                                     - 24 -A*A*
  1461. PA A
  1462.  
  1463.  
  1464.  
  1465.  
  1466.  
  1467.        {---------------------------------------------------------------}
  1468.        { Parse and Translate a Math Factor }
  1469.  
  1470.        procedure BoolExpression; Forward;
  1471.  
  1472.        procedure Factor;
  1473.        begin
  1474.           if Token = '(' then begin
  1475.              Next;
  1476.              BoolExpression;
  1477.              MatchString(')');
  1478.              end
  1479.           else begin
  1480.              if Token = 'x' then
  1481.                 LoadVar(Value)
  1482.              else if Token = '#' then
  1483.                 LoadConst(Value)
  1484.              else Expected('Math Factor');
  1485.              Next;
  1486.           end;
  1487.        end;
  1488.  
  1489.  
  1490.        {--------------------------------------------------------------}
  1491.        { Recognize and Translate a Multiply }
  1492.  
  1493.        procedure Multiply;
  1494.        begin
  1495.           Next;
  1496.           Factor;
  1497.           PopMul;
  1498.        end;
  1499.  
  1500.  
  1501.        {-------------------------------------------------------------}
  1502.        { Recognize and Translate a Divide }
  1503.  
  1504.        procedure Divide;
  1505.        begin
  1506.           Next;
  1507.           Factor;
  1508.           PopDiv;
  1509.        end;
  1510.  
  1511.  
  1512.        {---------------------------------------------------------------}
  1513.        { Parse and Translate a Math Term }
  1514.  
  1515.        procedure Term;
  1516.        begin
  1517.           Factor;
  1518.           while IsMulop(Token) do begin
  1519.              Push;
  1520.              case Token ofA*A*
  1521.                                     - 25 -
  1522. PA A
  1523.  
  1524.  
  1525.  
  1526.  
  1527.  
  1528.               '*': Multiply;
  1529.               '/': Divide;
  1530.              end;
  1531.           end;
  1532.        end;
  1533.  
  1534.  
  1535.        {--------------------------------------------------------------}
  1536.        { Recognize and Translate an Add }
  1537.  
  1538.        procedure Add;
  1539.        begin
  1540.           Next;
  1541.           Term;
  1542.           PopAdd;
  1543.        end;
  1544.  
  1545.  
  1546.        {-------------------------------------------------------------}
  1547.        { Recognize and Translate a Subtract }
  1548.  
  1549.        procedure Subtract;
  1550.        begin
  1551.           Next;
  1552.           Term;
  1553.           PopSub;
  1554.        end;
  1555.  
  1556.  
  1557.        {---------------------------------------------------------------}
  1558.        { Parse and Translate an Expression }
  1559.  
  1560.        procedure Expression;
  1561.        begin
  1562.           if IsAddop(Token) then
  1563.              Clear
  1564.           else
  1565.              Term;
  1566.           while IsAddop(Token) do begin
  1567.              Push;
  1568.              case Token of
  1569.               '+': Add;
  1570.               '-': Subtract;
  1571.              end;
  1572.           end;
  1573.        end;
  1574.  
  1575.  
  1576.        {---------------------------------------------------------------}
  1577.        { Get Another Expression and Compare }
  1578.  
  1579.        procedure CompareExpression;
  1580.        begin
  1581.           Expression;A*A*
  1582.                                     - 26 -
  1583. PA A
  1584.  
  1585.  
  1586.  
  1587.  
  1588.  
  1589.           PopCompare;
  1590.        end;
  1591.  
  1592.  
  1593.        {---------------------------------------------------------------}
  1594.        { Get The Next Expression and Compare }
  1595.  
  1596.        procedure NextExpression;
  1597.        begin
  1598.           Next;
  1599.           CompareExpression;
  1600.        end;
  1601.  
  1602.  
  1603.        {---------------------------------------------------------------}
  1604.        { Recognize and Translate a Relational "Equals" }
  1605.  
  1606.        procedure Equal;
  1607.        begin
  1608.           NextExpression;
  1609.           SetEqual;
  1610.        end;
  1611.  
  1612.  
  1613.        {---------------------------------------------------------------}
  1614.        { Recognize and Translate a Relational "Less Than or Equal" }
  1615.  
  1616.        procedure LessOrEqual;
  1617.        begin
  1618.           NextExpression;
  1619.           SetLessOrEqual;
  1620.        end;
  1621.  
  1622.  
  1623.        {---------------------------------------------------------------}
  1624.        { Recognize and Translate a Relational "Not Equals" }
  1625.  
  1626.        procedure NotEqual;
  1627.        begin
  1628.           NextExpression;
  1629.           SetNEqual;
  1630.        end;
  1631.  
  1632.  
  1633.        {---------------------------------------------------------------}
  1634.        { Recognize and Translate a Relational "Less Than" }
  1635.  
  1636.        procedure Less;
  1637.        begin
  1638.           Next;
  1639.           case Token of
  1640.             '=': LessOrEqual;
  1641.             '>': NotEqual;
  1642.           else beginA*A*
  1643.                                     - 27 -
  1644. PA A
  1645.  
  1646.  
  1647.  
  1648.  
  1649.  
  1650.                   CompareExpression;
  1651.                   SetLess;
  1652.                end;
  1653.           end;
  1654.        end;
  1655.  
  1656.  
  1657.        {---------------------------------------------------------------}
  1658.        { Recognize and Translate a Relational "Greater Than" }
  1659.  
  1660.        procedure Greater;
  1661.        begin
  1662.           Next;
  1663.           if Token = '=' then begin
  1664.              NextExpression;
  1665.              SetGreaterOrEqual;
  1666.              end
  1667.           else begin
  1668.              CompareExpression;
  1669.              SetGreater;
  1670.           end;
  1671.        end;
  1672.  
  1673.  
  1674.        {---------------------------------------------------------------}
  1675.        { Parse and Translate a Relation }
  1676.  
  1677.  
  1678.        procedure Relation;
  1679.        begin
  1680.           Expression;
  1681.           if IsRelop(Token) then begin
  1682.              Push;
  1683.              case Token of
  1684.               '=': Equal;
  1685.               '<': Less;
  1686.               '>': Greater;
  1687.              end;
  1688.           end;
  1689.        end;
  1690.  
  1691.  
  1692.        {---------------------------------------------------------------}
  1693.        { Parse and Translate a Boolean Factor with Leading NOT }
  1694.  
  1695.        procedure NotFactor;
  1696.        begin
  1697.           if Token = '!' then begin
  1698.              Next;
  1699.              Relation;
  1700.              NotIt;
  1701.              end
  1702.           else
  1703.              Relation;A*A*
  1704.                                     - 28 -
  1705. PA A
  1706.  
  1707.  
  1708.  
  1709.  
  1710.  
  1711.        end;
  1712.  
  1713.  
  1714.        {---------------------------------------------------------------}
  1715.        { Parse and Translate a Boolean Term }
  1716.  
  1717.        procedure BoolTerm;
  1718.        begin
  1719.           NotFactor;
  1720.           while Token = '&' do begin
  1721.              Push;
  1722.              Next;
  1723.              NotFactor;
  1724.              PopAnd;
  1725.           end;
  1726.        end;
  1727.  
  1728.  
  1729.        {--------------------------------------------------------------}
  1730.        { Recognize and Translate a Boolean OR }
  1731.  
  1732.        procedure BoolOr;
  1733.        begin
  1734.           Next;
  1735.           BoolTerm;
  1736.           PopOr;
  1737.        end;
  1738.  
  1739.  
  1740.        {--------------------------------------------------------------}
  1741.        { Recognize and Translate an Exclusive Or }
  1742.  
  1743.        procedure BoolXor;
  1744.        begin
  1745.           Next;
  1746.           BoolTerm;
  1747.           PopXor;
  1748.        end;
  1749.  
  1750.  
  1751.        {---------------------------------------------------------------}
  1752.        { Parse and Translate a Boolean Expression }
  1753.  
  1754.        procedure BoolExpression;
  1755.        begin
  1756.           BoolTerm;
  1757.           while IsOrOp(Token) do begin
  1758.              Push;
  1759.              case Token of
  1760.               '|': BoolOr;
  1761.               '~': BoolXor;
  1762.              end;
  1763.           end;
  1764.        end;A*A*
  1765.                                     - 29 -
  1766. PA A
  1767.  
  1768.  
  1769.  
  1770.  
  1771.  
  1772.        {--------------------------------------------------------------}
  1773.        { Parse and Translate an Assignment Statement }
  1774.  
  1775.        procedure Assignment;
  1776.        var Name: string;
  1777.        begin
  1778.           CheckTable(Value);
  1779.           Name := Value;
  1780.           Next;
  1781.           MatchString('=');
  1782.           BoolExpression;
  1783.           Store(Name);
  1784.        end;
  1785.  
  1786.  
  1787.        {---------------------------------------------------------------}
  1788.        { Recognize and Translate an IF Construct }
  1789.  
  1790.        procedure Block; Forward;
  1791.  
  1792.        procedure DoIf;
  1793.        var L1, L2: string;
  1794.        begin
  1795.           Next;
  1796.           BoolExpression;
  1797.           L1 := NewLabel;
  1798.           L2 := L1;
  1799.           BranchFalse(L1);
  1800.           Block;
  1801.           if Token = 'l' then begin
  1802.              Next;
  1803.              L2 := NewLabel;
  1804.              Branch(L2);
  1805.              PostLabel(L1);
  1806.              Block;
  1807.           end;
  1808.           PostLabel(L2);
  1809.           MatchString('ENDIF');
  1810.        end;
  1811.  
  1812.  
  1813.        {--------------------------------------------------------------}
  1814.        { Parse and Translate a WHILE Statement }
  1815.  
  1816.        procedure DoWhile;
  1817.        var L1, L2: string;
  1818.        begin
  1819.           Next;
  1820.           L1 := NewLabel;
  1821.           L2 := NewLabel;
  1822.           PostLabel(L1);
  1823.           BoolExpression;
  1824.           BranchFalse(L2);
  1825.           Block;A*A*
  1826.                                     - 30 -
  1827. PA A
  1828.  
  1829.  
  1830.  
  1831.  
  1832.  
  1833.           MatchString('ENDWHILE');
  1834.           Branch(L1);
  1835.           PostLabel(L2);
  1836.        end;
  1837.  
  1838.  
  1839.        {--------------------------------------------------------------}
  1840.        { Read a Single Variable }
  1841.  
  1842.        procedure ReadVar;
  1843.        begin
  1844.           CheckIdent;
  1845.           CheckTable(Value);
  1846.           ReadIt(Value);
  1847.           Next;
  1848.        end;
  1849.  
  1850.  
  1851.        {--------------------------------------------------------------}
  1852.        { Process a Read Statement }
  1853.  
  1854.        procedure DoRead;
  1855.        begin
  1856.           Next;
  1857.           MatchString('(');
  1858.           ReadVar;
  1859.           while Token = ',' do begin
  1860.              Next;
  1861.              ReadVar;
  1862.           end;
  1863.           MatchString(')');
  1864.        end;
  1865.  
  1866.  
  1867.        {--------------------------------------------------------------}
  1868.        { Process a Write Statement }
  1869.  
  1870.        procedure DoWrite;
  1871.        begin
  1872.           Next;
  1873.           MatchString('(');
  1874.           Expression;
  1875.           WriteIt;
  1876.           while Token = ',' do begin
  1877.              Next;
  1878.              Expression;
  1879.              WriteIt;
  1880.           end;
  1881.           MatchString(')');
  1882.        end;
  1883.  
  1884.  
  1885.        {--------------------------------------------------------------}
  1886.        { Parse and Translate a Block of Statements }A*A*
  1887.                                     - 31 -
  1888. PA A
  1889.  
  1890.  
  1891.  
  1892.  
  1893.  
  1894.        procedure Block;
  1895.        begin
  1896.           Scan;
  1897.           while not(Token in ['e', 'l']) do begin
  1898.              case Token of
  1899.               'i': DoIf;
  1900.               'w': DoWhile;
  1901.               'R': DoRead;
  1902.               'W': DoWrite;
  1903.              else Assignment;
  1904.              end;
  1905.              Scan;
  1906.           end;
  1907.        end;
  1908.  
  1909.  
  1910.        {--------------------------------------------------------------}
  1911.        { Allocate Storage for a Variable }
  1912.  
  1913.        procedure Alloc;
  1914.        begin
  1915.           Next;
  1916.           if Token <> 'x' then Expected('Variable Name');
  1917.           CheckDup(Value);
  1918.           AddEntry(Value, 'v');
  1919.           Allocate(Value, '0');
  1920.           Next;
  1921.        end;
  1922.  
  1923.  
  1924.        {--------------------------------------------------------------}
  1925.        { Parse and Translate Global Declarations }
  1926.  
  1927.        procedure TopDecls;
  1928.        begin
  1929.           Scan;
  1930.           while Token = 'v' do
  1931.              Alloc;
  1932.              while Token = ',' do
  1933.                 Alloc;
  1934.        end;
  1935.  
  1936.  
  1937.        {--------------------------------------------------------------}
  1938.        { Initialize }
  1939.  
  1940.        procedure Init;
  1941.        begin
  1942.           GetChar;
  1943.           Next;
  1944.        end;
  1945.  
  1946.  
  1947.        {--------------------------------------------------------------}A*A*
  1948.                                     - 32 -
  1949. PA A
  1950.  
  1951.  
  1952.  
  1953.  
  1954.  
  1955.        { Main Program }
  1956.  
  1957.        begin
  1958.           Init;
  1959.           MatchString('PROGRAM');
  1960.           Header;
  1961.           TopDecls;
  1962.           MatchString('BEGIN');
  1963.           Prolog;
  1964.           Block;
  1965.           MatchString('END');
  1966.           Epilog;
  1967.        end.
  1968.        {--------------------------------------------------------------}AUAU
  1969.  
  1970.  
  1971.  
  1972.  
  1973.  
  1974. A A
  1975.                                     - 33 -A*A*
  1976. @